/*
 *  ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd).
 *  s_log.c: Logger functions.
 *
 *  Copyright (C) 2002 by the past and present ircd coders, and others.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 *  USA
 *
 *  $Id: s_log.c 369 2006-05-02 21:34:55Z jon $
 */

#include "stdinc.h"

#ifdef USE_SYSLOG
# ifdef HAVE_SYS_SYSLOG_H
#  include <sys/syslog.h>
# else
#  ifdef HAVE_SYSLOG_H
#   include <syslog.h>
#  endif
# endif
#endif

#include "client.h"		/* Needed for struct Client */
#include "common.h"
#include "s_log.h"
#include "fileio.h"
#include "irc_string.h"
#include "sprintf_irc.h"
#include "ircd.h"
#include "s_misc.h"
#include "event.h"		/* Needed for EVH etc. */
#include "s_conf.h"
#include "memory.h"

/* some older syslogs would overflow at 2024 */
#define LOG_BUFSIZE 2000

static FBFILE *logFile;
static int logLevel = INIT_LOG_LEVEL;

#ifndef SYSLOG_USERS
static EVH user_log_resync;
static FBFILE *user_log_fb = NULL;
#endif


#ifdef USE_SYSLOG
static const int sysLogLevel[] = {
	LOG_CRIT,
	LOG_ERR,
	LOG_WARNING,
	LOG_NOTICE,
	LOG_INFO,
	LOG_INFO,
	LOG_INFO
};
#endif

static const char *logLevelToString[] = {
	"L_CRIT",
	"L_ERROR",
	"L_WARN",
	"L_NOTICE",
	"L_TRACE",
	"L_INFO",
	"L_DEBUG"
};

/*
 * open_log - open ircd logging file
 * returns true (1) if successful, false (0) otherwise
 */
static int
open_log(const char *filename)
{
	logFile = fbopen(filename, "a");

	if(logFile == NULL)
	{
#ifdef USE_SYSLOG
		syslog(LOG_ERR, "Unable to open log file: %s: %s", filename, strerror(errno));
#endif
		return (0);
	}

	return (1);
}

static void
write_log(const char *message)
{
	char buf[LOG_BUFSIZE];
	size_t nbytes = 0;

	if(logFile == NULL)
		return;

#ifdef _WIN32
	nbytes = snprintf(buf, sizeof(buf), "[%s] %s\r\n", smalldate(CurrentTime), message);
#else
	nbytes = snprintf(buf, sizeof(buf), "[%s] %s\n", smalldate(CurrentTime), message);
#endif
	fbputs(buf, logFile, nbytes);
}

void
ilog(const int priority, const char *fmt, ...)
{
	char buf[LOG_BUFSIZE];
	va_list args;

	assert(priority > -1);

	if(fmt == NULL)
		return;

	if(priority > logLevel)
		return;

	va_start(args, fmt);
	vsprintf(buf, fmt, args);
	va_end(args);

#ifdef USE_SYSLOG
	if(priority <= L_DEBUG)
		syslog(sysLogLevel[priority], "%s", buf);
#endif
	if(ConfigLoggingEntry.use_logging)
		write_log(buf);
}

void
init_log(const char *filename)
{
	open_log(filename);
#ifdef USE_SYSLOG
	openlog("ircd", LOG_PID | LOG_NDELAY, LOG_FACILITY);
#endif
#ifndef SYSLOG_USERS
	eventAddIsh("user_log_resync", user_log_resync, NULL, 60);
#endif
}

void
reopen_log(const char *filename)
{
	if(logFile != NULL)
		fbclose(logFile);
	open_log(filename);
}

void
set_log_level(const int level)
{
	if(L_ERROR < level && level <= L_DEBUG)
		logLevel = level;
}

int
get_log_level(void)
{
	return (logLevel);
}

const char *
get_log_level_as_string(int level)
{
	if(level > L_DEBUG)
		level = L_DEBUG;
	else if(level < L_ERROR)
		level = L_ERROR;

	return (logLevelToString[level]);
}

/* log_user_exit()
 *
 * inputs	- pointer to connecting client
 * output	- NONE
 * side effects - Current exiting client is logged to
 *		  either SYSLOG or to file.
 */
void
log_user_exit(struct Client *source_p)
{
	time_t on_for = CurrentTime - source_p->firsttime;
#ifdef SYSLOG_USERS
	if(IsClient(source_p))
	{
		ilog(L_INFO, "%s (%3u:%02u:%02u): %s!%s@%s %llu/%llu",
		     myctime(source_p->firsttime),
		     (unsigned int) (on_for / 3600),
		     (unsigned int) ((on_for % 3600) / 60),
		     (unsigned int) (on_for % 60),
		     source_p->name, source_p->username, source_p->realhost,
		     source_p->localClient->send.bytes >> 10,
		     source_p->localClient->recv.bytes >> 10);
	}
#else
	{
		char linebuf[BUFSIZ];

		/*
		 * This conditional makes the logfile active only after
		 * it's been created - thus logging can be turned off by
		 * removing the file.
		 * -Taner
		 */
		if(IsClient(source_p))
		{
			if(user_log_fb == NULL)
			{
				if((ConfigLoggingEntry.userlog[0] != '\0') &&
				   (user_log_fb = fbopen(ConfigLoggingEntry.userlog, "r")) != NULL)
				{
					fbclose(user_log_fb);
					user_log_fb = fbopen(ConfigLoggingEntry.userlog, "a");
				}
			}

			if(user_log_fb != NULL)
			{
				size_t nbytes = ircsprintf(linebuf,
							   "%s (%3u:%02u:%02u): %s!%s@%s %llu/%llu\n",
							   myctime(source_p->firsttime),
							   (unsigned int) (on_for / 3600),
							   (unsigned int) ((on_for % 3600) / 60),
							   (unsigned int) (on_for % 60),
							   source_p->name, source_p->username,
							   source_p->realhost,
							   source_p->localClient->send.bytes >> 10,
							   source_p->localClient->recv.bytes >> 10);
				fbputs(linebuf, user_log_fb, nbytes);
			}
		}
	}
#endif
}

#ifndef SYSLOG_USERS
/* user_log_resync()
 *
 * inputs	- NONE
 * output	- NONE
 * side effects	-
 */
static void
user_log_resync(void *notused)
{
	if(user_log_fb != NULL)
	{
		fbclose(user_log_fb);
		user_log_fb = NULL;
	}
}
#endif

/* log_oper_action()
 *
 * inputs	- type of oper log entry
 *		- pointer to oper
 *              - const char *pattern == format string
 *		- var args for format string
 * output	- none
 * side effects - corresponding log is written to, if its present.
 *
 * rewritten sept 5 2005 - Dianora
 */
void
log_oper_action(int log_type, const struct Client *source_p, const char *pattern, ...)
{
	va_list args;
	char linebuf[IRCD_BUFSIZE];
	FBFILE *log_fb;
	char *logfile;
	const char *log_message;
	size_t nbytes;
	size_t n_preamble;
	char *p;

	switch (log_type)
	{
	case LOG_OPER_TYPE:
		logfile = ConfigLoggingEntry.operlog;
		log_message = "OPER";
		break;
	case LOG_FAILED_OPER_TYPE:
		logfile = ConfigLoggingEntry.failed_operlog;
		log_message = "FAILED OPER";
		break;
	case LOG_KLINE_TYPE:
		logfile = ConfigLoggingEntry.klinelog;
		log_message = "KLINE";
		break;
	case LOG_RKLINE_TYPE:
		logfile = ConfigLoggingEntry.klinelog;
		log_message = "RKLINE";
		break;
	case LOG_DLINE_TYPE:
		logfile = ConfigLoggingEntry.klinelog;
		log_message = "DLINE";
		break;
	case LOG_TEMP_DLINE_TYPE:
		logfile = ConfigLoggingEntry.klinelog;
		log_message = "TEMP DLINE";
		break;
	case LOG_TEMP_KLINE_TYPE:
		logfile = ConfigLoggingEntry.klinelog;
		log_message = "TEMP KLINE";
		break;
	case LOG_GLINE_TYPE:
		logfile = ConfigLoggingEntry.glinelog;
		log_message = "GLINE";
		break;
	case LOG_KILL_TYPE:
		logfile = ConfigLoggingEntry.killlog;
		log_message = "KILL";
		break;
	case LOG_IOERR_TYPE:
		logfile = ConfigLoggingEntry.ioerrlog;
		log_message = "IO ERR";
		break;
	default:
		return;
	}

	if(*logfile == '\0')
		return;

	p = linebuf;
	if(source_p != NULL)
	{
		n_preamble = ircsprintf(linebuf, "%s %s by (%s!%s@%s) :",
					myctime(CurrentTime), log_message,
					source_p->name, source_p->username, source_p->host);

	}
	else
	{
		n_preamble = ircsprintf(linebuf, "%s %s :", myctime(CurrentTime), log_message);
	}

	p += n_preamble;

	if((log_fb = fbopen(logfile, "r")) != NULL)
	{
		fbclose(log_fb);
		log_fb = fbopen(logfile, "a");
		if(log_fb == NULL)
			return;
		va_start(args, pattern);
		/* XXX add check for IRCD_BUFSIZE-(n_preamble+1) < 0 ? -db */
		nbytes = vsnprintf(p, IRCD_BUFSIZE - (n_preamble + 1), pattern, args);
		nbytes += n_preamble;
		va_end(args);
		fbputs(linebuf, log_fb, nbytes);
		fbclose(log_fb);
	}
}
